package com.xxx.interceptor;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import com.github.pagehelper.Dialect;
import com.github.pagehelper.PageException;
import com.github.pagehelper.cache.Cache;
import com.github.pagehelper.cache.CacheFactory;
import com.github.pagehelper.util.MSUtils;
import com.github.pagehelper.util.StringUtil;

/**
 * 通用分页拦截器（重写版）
 *
 * @author CSDN博客：seesun2012
 *
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
@Intercepts({
		@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
				RowBounds.class, ResultHandler.class }),
		@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
				RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class }) })
public class PageInterceptor implements Interceptor {

	protected Cache<CacheKey, MappedStatement> msCountMap = null;
	private Dialect dialect;
	private String default_dialect_class = "com.github.pagehelper.PageHelper";
	private Field additionalParametersField;

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		try {
			Object[] args = invocation.getArgs();
			MappedStatement ms = (MappedStatement) args[0];
			List<ResultMap> map = ms.getResultMaps();
			Set<String> columns = map.get(0).getMappedColumns();
			SqlCommandType sqlCommandType = ms.getSqlCommandType();
			Object parameter = args[1];
			Object pageParameter = args[1];
			RowBounds rowBounds = (RowBounds) args[2];
			ResultHandler resultHandler = (ResultHandler) args[3];
			Executor executor = (Executor) invocation.getTarget();
			CacheKey cacheKey;
			BoundSql boundSql;
			if (args.length == 4) {
				boundSql = ms.getBoundSql(parameter);
				cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
			} else {
				cacheKey = (CacheKey) args[4];
				boundSql = (BoundSql) args[5];
			}
			List resultList;
			if (!dialect.skip(ms, parameter, rowBounds)) {
				Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField
						.get(boundSql);
				if (dialect.beforeCount(ms, parameter, rowBounds)) {
					CacheKey countKey = executor.createCacheKey(ms, parameter, RowBounds.DEFAULT, boundSql);
					countKey.update("_Count");
					MappedStatement countMs = msCountMap.get(countKey);
					if (countMs == null) {
						countMs = MSUtils.newCountMappedStatement(ms);
						msCountMap.put(countKey, countMs);
					}
					String countSql = dialect.getCountSql(ms, boundSql, parameter, rowBounds, countKey);
					BoundSql countBoundSql = new BoundSql(ms.getConfiguration(), countSql,
							boundSql.getParameterMappings(), parameter);
					for (String key : additionalParameters.keySet()) {
						countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
					}
					Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler,
							countKey, countBoundSql);
					Long count = (Long) ((List) countResultList).get(0);
					if (!dialect.afterCount(count, parameter, rowBounds)) {
						return dialect.afterPage(new ArrayList(), parameter, rowBounds);
					}
				}
				if (dialect.beforePage(ms, parameter, rowBounds)) {
					CacheKey pageKey = cacheKey;
					parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
					String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
					BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql,
							boundSql.getParameterMappings(), parameter);
					for (String key : additionalParameters.keySet()) {
						pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
					}
					resultList = executor.query(ms, pageParameter, RowBounds.DEFAULT, resultHandler, pageKey,
							pageBoundSql);
				} else {
					resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
				}
			} else {
				resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
			}
			/** ============================ result结果集解密 ============================ **/
			for (Object obj : resultList) {
				if (obj instanceof CommonEntity) {
					CommonEntity.skill(obj, sqlCommandType.name());
				}
			}
			boolean hasTab = CommonEntity.checkTable(boundSql.getSql());
			
			/** ================== 执行参数解密：这里返回回来的是密文，一旦被再次调用就是大坑 ================== **/
			CommonEntity.decodelumn(hasTab, parameter, columns, ms.getSqlCommandType().name());
			return dialect.afterPage(resultList, parameter, rowBounds);
		} finally {
			dialect.afterAll();
		}
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {
		msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
		String dialectClass = properties.getProperty("dialect");
		if (StringUtil.isEmpty(dialectClass)) {
			dialectClass = default_dialect_class;
		}
		try {
			Class<?> aClass = Class.forName(dialectClass);
			dialect = (Dialect) aClass.newInstance();
		} catch (Exception e) {
			throw new PageException(e);
		}
		dialect.setProperties(properties);
		try {
			additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
			additionalParametersField.setAccessible(true);
		} catch (NoSuchFieldException e) {
			throw new PageException(e);
		}
	}

}
